package com.stars.easyms.datasource.batch;

import com.alibaba.fastjson.JSON;
import com.stars.easyms.base.batch.BatchResult;
import com.stars.easyms.datasource.EasyMsDataSource;
import com.stars.easyms.datasource.EasyMsMasterSlaveDataSource;
import com.stars.easyms.datasource.EasyMsMultiDataSource;
import com.stars.easyms.datasource.common.EasyMsDataSourceConstant;
import com.stars.easyms.datasource.common.SqlExceptionErrorCodeConstant;
import com.stars.easyms.datasource.enums.DatabaseType;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.SimpleExecutor;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.transaction.SpringManagedTransaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.sql.*;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 批量提交
 *
 * @author guoguifang
 * @date 2018-10-19 10:07
 * @since 1.0.0
 */
public final class BatchCommit {

    private static final Logger logger = LoggerFactory.getLogger(BatchCommit.class);

    private static final int PER_COMMIT_SIZE = 3000;

    private static final String MYSQL_BATCH_PARAM = "rewriteBatchedStatements";

    private static final ThreadLocal<TransactionStatus> TRANSACTION_STATUS = new ThreadLocal<>();

    private final SqlSessionFactory sqlSessionFactory;

    private final EasyMsMultiDataSource easyMsMultiDataSource;

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchInsert(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchInsert(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchInsert(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchInsert(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String datasourceName, String sqlId, List<T> list) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String sqlId, List<T> list) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量插入(非整体提交/回滚)，若批量插入失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchInsert(String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchInsertAsWhole(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchInsertAsWhole(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchInsertAsWhole(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchInsertAsWhole(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String datasourceName, String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量插入(作为一个整体提交/回滚)，若批量插入有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchInsertAsWhole(String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchUpdate(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchUpdate(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchUpdate(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchUpdate(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String datasourceName, String sqlId, List<T> list) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String sqlId, List<T> list) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量修改(非整体提交/回滚)，若批量修改失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchUpdate(String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchUpdateAsWhole(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchUpdateAsWhole(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchUpdateAsWhole(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchUpdateAsWhole(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String datasourceName, String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量修改(作为一个整体提交/回滚)，若批量修改有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchUpdateAsWhole(String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchDelete(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchDelete(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchDelete(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchDelete(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String datasourceName, String sqlId, List<T> list) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String sqlId, List<T> list) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, false);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量删除(非整体提交/回滚)，若批量删除失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchDelete(String sqlId, List<T> list, int perCommitSize) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, false);
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list) {
        return this.batchDeleteAsWhole(datasourceName, daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass   DAO类
     * @param methodName 方法名
     * @param list       数据集合
     * @param <T>        数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(Class<?> daoClass, String methodName, List<T> list) {
        return this.batchDeleteAsWhole(daoClass.getName() + "." + methodName, list);
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param daoClass       DAO类
     * @param methodName     方法名
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String datasourceName, Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchDeleteAsWhole(datasourceName, daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param daoClass      DAO类
     * @param methodName    方法名
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(Class<?> daoClass, String methodName, List<T> list, int perCommitSize) {
        return this.batchDeleteAsWhole(daoClass.getName() + "." + methodName, list, perCommitSize);
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String datasourceName, String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list  数据集合
     * @param <T>   数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String sqlId, List<T> list) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName 指定数据源名称
     * @param sqlId          mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list           数据集合
     * @param perCommitSize  每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>            数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String datasourceName, String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(datasourceName, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量删除(作为一个整体提交/回滚)，若批量删除有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param sqlId         mybatis的xml文件中的namespace+"."+sqlId为完整sqlId
     * @param list          数据集合
     * @param perCommitSize 每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>           数据类型
     * @return 批量处理结果：true：处理成功，false：处理失败
     */
    public <T> boolean batchDeleteAsWhole(String sqlId, List<T> list, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, sqlId, convertOrdinalMap(list), perCommitSize, true);
        return batchResult.getSuccessCount() == list.size();
    }

    /**
     * 批量提交(非整体提交/回滚)，若批量提交失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName   指定数据源名称
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchCommit(String datasourceName, List<BatchDmlUnit<T>> batchDmlUnitList) {
        return this.batchCommit(datasourceName, batchDmlUnitList, PER_COMMIT_SIZE, false);
    }

    /**
     * 批量提交(非整体提交/回滚)，若批量提交失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchCommit(List<BatchDmlUnit<T>> batchDmlUnitList) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, batchDmlUnitList, PER_COMMIT_SIZE, false);
    }

    /**
     * 批量提交(非整体提交/回滚)，若批量提交失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是指定数据源的主库
     *
     * @param datasourceName   指定数据源名称
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param perCommitSize    每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchCommit(String datasourceName, List<BatchDmlUnit<T>> batchDmlUnitList, int perCommitSize) {
        return this.batchCommit(datasourceName, batchDmlUnitList, perCommitSize, false);
    }

    /**
     * 批量提交(非整体提交/回滚)，若批量提交失败时按单条操作（单条操作时性能可能会有降低）
     * 使用的是默认数据源的主库
     *
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param perCommitSize    每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> BatchResult<T> batchCommit(List<BatchDmlUnit<T>> batchDmlUnitList, int perCommitSize) {
        return this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, batchDmlUnitList, perCommitSize, false);
    }

    /**
     * 批量提交(作为一个整体提交/回滚)，若批量提交有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName   指定数据源名称
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> boolean batchCommitAsWhole(String datasourceName, List<BatchDmlUnit<T>> batchDmlUnitList) {
        BatchResult batchResult = this.batchCommit(datasourceName, batchDmlUnitList, PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == batchDmlUnitList.size();
    }

    /**
     * 批量提交(作为一个整体提交/回滚)，若批量提交有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> boolean batchCommitAsWhole(List<BatchDmlUnit<T>> batchDmlUnitList) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, batchDmlUnitList, PER_COMMIT_SIZE, true);
        return batchResult.getSuccessCount() == batchDmlUnitList.size();
    }

    /**
     * 批量提交(作为一个整体提交/回滚)，若批量提交有失败的情况则整体回滚
     * 使用的是指定数据源的主库
     *
     * @param datasourceName   指定数据源名称
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param perCommitSize    每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> boolean batchCommitAsWhole(String datasourceName, List<BatchDmlUnit<T>> batchDmlUnitList, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(datasourceName, batchDmlUnitList, perCommitSize, true);
        return batchResult.getSuccessCount() == batchDmlUnitList.size();
    }

    /**
     * 批量提交(作为一个整体提交/回滚)，若批量提交有失败的情况则整体回滚
     * 使用的是默认数据源的主库
     *
     * @param batchDmlUnitList 批量的DML（增删改）数据单元集合
     * @param perCommitSize    每次最大提交数量，例如设置1000，当数据为3000时将分三次提交，默认值为3000
     * @param <T>              数据类型
     * @return 批量处理结果：可获取哪些数据处理成功，哪些数据处理失败，结果将按照传入list的顺序返回结果
     */
    public <T> boolean batchCommitAsWhole(List<BatchDmlUnit<T>> batchDmlUnitList, int perCommitSize) {
        BatchResult batchResult = this.batchCommit(EasyMsDataSourceConstant.DEFAULT_DATASOURCE_NAME, batchDmlUnitList, perCommitSize, true);
        return batchResult.getSuccessCount() == batchDmlUnitList.size();
    }

    private <T> BatchResult<T> batchCommit(String datasourceName, List<BatchDmlUnit<T>> batchDmlUnitList, int perCommitSize, boolean asWhole) {
        BatchResult<T> batchResult = new BatchResult<>();
        int listSize;
        if (batchDmlUnitList != null && (listSize = batchDmlUnitList.size()) > 0) {
            Map<String, Map<Integer, T>> batchOrdinalMap = new HashMap<>(64);
            for (int i = 0; i < listSize; i++) {
                BatchDmlUnit<T> batchDmlUnit = batchDmlUnitList.get(i);
                batchOrdinalMap.computeIfAbsent(batchDmlUnit.getSqlId(), key -> new TreeMap<>()).put(i, batchDmlUnit.getObj());
            }
            String currSqlId = null;
            Map<Integer, T> currOrdinalMap = null;
            BatchCommitConfig baseBatchCommitConfig = new BatchCommitConfig(datasourceName, perCommitSize, asWhole);
            if (!baseBatchCommitConfig.useBatch) {
                logger.warn("Please add the parameter '{}' to the dataSource '{}' URL and set it to true, otherwise batch operation cannot be used.",
                        MYSQL_BATCH_PARAM, datasourceName);
            }
            try {
                for (Map.Entry<String, Map<Integer, T>> entry : batchOrdinalMap.entrySet()) {
                    currSqlId = entry.getKey();
                    currOrdinalMap = entry.getValue();
                    BatchCommitConfig batchCommitConfig = new BatchCommitConfig(baseBatchCommitConfig, currSqlId);
                    BatchResult<T> subBatchResult = intactBatchCommit(batchCommitConfig, currOrdinalMap);
                    batchResult.addSuccessDatas(subBatchResult.getSuccessOrdinalMap());
                    batchResult.addFailDatas(subBatchResult.getFailOrdinalMap());
                }
                commitTransaction(baseBatchCommitConfig);
            } catch (Exception e) {
                logger.error("Batch processing data failed, datasource name: {}, sqlId: {}, fail data: {}", datasourceName, currSqlId, toJsonString(currOrdinalMap), e);
                rollbackTransaction(baseBatchCommitConfig);
                batchResult.clearSuccessDatas();
                batchResult.clearFailDatas();
                batchOrdinalMap.values().forEach(batchResult::addFailDatas);
            } finally {
                TRANSACTION_STATUS.remove();
            }
        }
        return batchResult;
    }

    private <T> BatchResult<T> batchCommit(String datasourceName, String sqlId, Map<Integer, T> ordinalMap, int perCommitSize, boolean asWhole) {
        BatchResult<T> batchResult = new BatchResult<>();
        if (ordinalMap != null) {
            BatchCommitConfig batchCommitConfig = new BatchCommitConfig(datasourceName, sqlId, perCommitSize, asWhole);
            if (!batchCommitConfig.useBatch) {
                logger.warn("Please add the parameter '{}' to the dataSource '{}' URL and set it to true, otherwise batch operation cannot be used.",
                        MYSQL_BATCH_PARAM, datasourceName);
            }
            try {
                batchResult = intactBatchCommit(batchCommitConfig, ordinalMap);
                commitTransaction(batchCommitConfig);
            } catch (Exception e) {
                logger.error("Batch processing data failed, datasource name: {}, sqlId: {}, fail data: {}", datasourceName, sqlId, toJsonString(ordinalMap), e);
                rollbackTransaction(batchCommitConfig);
                batchResult.clearSuccessDatas();
                batchResult.clearFailDatas();
                batchResult.addFailDatas(ordinalMap);
            } finally {
                TRANSACTION_STATUS.remove();
            }
        }
        return batchResult;
    }

    private <T> BatchResult<T> intactBatchCommit(BatchCommitConfig batchCommitConfig, Map<Integer, T> ordinalMap) throws SQLException {
        final Map<Integer, T> failRetryMap = new TreeMap<>();
        BatchResult<T> batchResult = handleBatchCommit(batchCommitConfig, ordinalMap, failRetryMap, false);
        // 如果不是整体事务则失败后重新执行错误数据之后的数据
        while (failRetryMap.size() > 0) {
            Map<Integer, T> ordinalFailRetryMap = new TreeMap<>(failRetryMap);
            failRetryMap.clear();
            BatchResult<T> subBatchResult = handleBatchCommit(batchCommitConfig, ordinalFailRetryMap, failRetryMap, true);
            batchResult.addSuccessDatas(subBatchResult.getSuccessOrdinalMap());
            batchResult.addFailDatas(subBatchResult.getFailOrdinalMap());
        }
        return batchResult;
    }

    @SuppressWarnings("unchecked")
    private <T> BatchResult<T> handleBatchCommit(BatchCommitConfig batchCommitConfig, Map<Integer, T> ordinalMap,
                                                 Map<Integer, T> failRetryMap, boolean isFailRetry) throws SQLException {
        Map<String, List<PreparedStatementHolder>> preparedStatementMap = new HashMap<>(32);
        // 准备批量处理数据
        for (Map.Entry<Integer, T> entry : ordinalMap.entrySet()) {
            T t = entry.getValue();
            // 如果是失败后继续执行的不需要重新获取selectKey值
            if (!isFailRetry) {
                // 如果有selectKey则创建一个数据库连接获取selectKey的值并赋值给对象
                ErrorContext.instance().store();
                SpringManagedTransaction springManagedTransaction = new SpringManagedTransaction(batchCommitConfig.easyMsDataSource);
                batchCommitConfig.keyGenerator.processBefore(new SimpleExecutor(batchCommitConfig.configuration, springManagedTransaction),
                        batchCommitConfig.mappedStatement, null, t);
                ErrorContext.instance().recall();
                springManagedTransaction.close();
            }
            // 创建PreparedStatement对象，每一条SQL对应一个PreparedStatement对象
            BoundSql boundSql = batchCommitConfig.mappedStatement.getBoundSql(t);
            String sql = boundSql.getSql();
            List<PreparedStatementHolder> preparedStatementList = preparedStatementMap.get(sql);
            PreparedStatementHolder preparedStatementHolder = null;
            if (preparedStatementList == null) {
                preparedStatementList = new ArrayList<>();
                preparedStatementHolder = new PreparedStatementHolder(batchCommitConfig.connection.prepareStatement(sql));
                preparedStatementList.add(preparedStatementHolder);
                preparedStatementMap.put(sql, preparedStatementList);
            }
            if (preparedStatementHolder == null) {
                preparedStatementHolder = preparedStatementList.get(preparedStatementList.size() - 1);
            }
            DefaultParameterHandler defaultParameterHandler = new DefaultParameterHandler(batchCommitConfig.mappedStatement, t, boundSql);
            defaultParameterHandler.setParameters(preparedStatementHolder.preparedStatement);
            preparedStatementHolder.preparedStatement.addBatch();
            preparedStatementHolder.subOrdinalMap.put(entry.getKey(), t);
            if (preparedStatementHolder.count.incrementAndGet() == batchCommitConfig.perCommitSize) {
                preparedStatementList.add(new PreparedStatementHolder(batchCommitConfig.connection.prepareStatement(sql)));
            }
        }
        return handleBatchCommit(preparedStatementMap, batchCommitConfig, failRetryMap);
    }

    /**
     * 执行批量数据处理
     */
    @SuppressWarnings("unchecked")
    private <T> BatchResult<T> handleBatchCommit(Map<String, List<PreparedStatementHolder>> preparedStatementMap,
                                                 BatchCommitConfig batchCommitConfig, Map<Integer, T> failRetryMap) throws SQLException {
        BatchResult<T> batchResult = new BatchResult<>();
        for (List<PreparedStatementHolder> preparedStatementHolderList : preparedStatementMap.values()) {
            for (PreparedStatementHolder preparedStatementHolder : preparedStatementHolderList) {
                try {
                    // 批量一旦执行完无论有没有异常都立即释放游标
                    // 不在处理完异常后释放的原因是降低因游标占用时间过长在高并发情况下引起游标溢出的风险
                    // 若低概率游标溢出异常出现则使用单条提交的方式重新提交
                    try {
                        preparedStatementHolder.preparedStatement.executeBatch();
                    } finally {
                        preparedStatementHolder.preparedStatement.close();
                    }
                    batchResult.addSuccessDatas(preparedStatementHolder.subOrdinalMap);
                } catch (BatchUpdateException batchUpdateException) {
                    if (batchUpdateException.getUpdateCounts() != null) {
                        Map<Integer, T> retryMap = null;
                        if (DatabaseType.ORACLE == batchCommitConfig.databaseType) {
                            retryMap = handleOracleBatchUpdateException(batchCommitConfig, preparedStatementHolder.subOrdinalMap, batchUpdateException, batchResult);
                        } else if (DatabaseType.MYSQL_5 == batchCommitConfig.databaseType
                                || DatabaseType.MYSQL_8 == batchCommitConfig.databaseType) {
                            retryMap = handleMysqlBatchUpdateException(batchCommitConfig, preparedStatementHolder.subOrdinalMap, batchUpdateException, batchResult);
                        } else {
                            logger.error("Batch processing data failed, datasource name: {}, error code: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName,
                                    batchUpdateException.getErrorCode(), batchCommitConfig.sqlId, toJsonString(preparedStatementHolder.subOrdinalMap), batchUpdateException);
                            batchResult.addFailDatas(preparedStatementHolder.subOrdinalMap);
                        }
                        if (retryMap != null) {
                            failRetryMap.putAll(retryMap);
                        }
                    } else {
                        logger.error("Batch processing data failed, datasource name: {}, error code: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName,
                                batchUpdateException.getErrorCode(), batchCommitConfig.sqlId, toJsonString(preparedStatementHolder.subOrdinalMap), batchUpdateException);
                        batchResult.addFailDatas(preparedStatementHolder.subOrdinalMap);
                    }
                } catch (Exception exception) {
                    if (batchCommitConfig.asWhole) {
                        throw exception;
                    }
                    logger.error("Batch processing data failed, datasource name: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName, batchCommitConfig.sqlId,
                            toJsonString(preparedStatementHolder.subOrdinalMap), exception);
                    batchResult.addFailDatas(preparedStatementHolder.subOrdinalMap);
                }
            }
        }
        return batchResult;
    }

    /**
     * 处理oracle批量提交异常
     */
    private <T> Map<Integer, T> handleOracleBatchUpdateException(BatchCommitConfig batchCommitConfig, Map<Integer, T> ordinalMap,
                                                                 BatchUpdateException batchUpdateException, BatchResult<T> batchResult) throws BatchUpdateException {
        boolean isSingleCommit = checkBatchUpdateException(batchCommitConfig, batchUpdateException);
        int ordinalMapSize = ordinalMap.size();
        Map<Integer, T> retryMap = null;
        if (isSingleCommit) {
            for (int i = 0; i < ordinalMapSize; i++) {
                Map<Integer, T> subMap = subMap(ordinalMap, i, null);
                if (singleCommit(batchCommitConfig, subMap.values().toArray()[0])) {
                    batchResult.addSuccessDatas(subMap);
                } else {
                    batchResult.addFailDatas(subMap);
                }
            }
        } else {
            int[] updateCounts = batchUpdateException.getUpdateCounts();
            int successLength = updateCounts.length;
            if (successLength > 0 && updateCounts[successLength - 1] == SqlExceptionErrorCodeConstant.SQL_EXECUTE_FAIL_STATUS) {
                batchResult.addSuccessDatas(subMap(ordinalMap, 0, successLength - 1));
                Map<Integer, T> subMap = subMap(ordinalMap, successLength - 1, null);
                if (!subMap.isEmpty()) {
                    if (logger.isErrorEnabled()) {
                        logger.error("Batch processing data failed, datasource name: {}, error code: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName,
                                batchUpdateException.getErrorCode(), batchCommitConfig.sqlId, toJsonString(subMap), batchUpdateException);
                    }
                    batchResult.addFailDatas(subMap);
                }
                if (ordinalMapSize > successLength) {
                    retryMap = subMap(ordinalMap, successLength, ordinalMapSize);
                }
            } else {
                batchResult.addSuccessDatas(subMap(ordinalMap, 0, successLength));
                if (ordinalMapSize > successLength) {
                    Map<Integer, T> subMap = subMap(ordinalMap, successLength, null);
                    if (!subMap.isEmpty()) {
                        if (logger.isErrorEnabled()) {
                            logger.error("Batch processing data failed, datasource name: {}, error code: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName,
                                    batchUpdateException.getErrorCode(), batchCommitConfig.sqlId, toJsonString(subMap), batchUpdateException);
                        }
                        batchResult.addFailDatas(subMap);
                    }
                    if (successLength + 1 < ordinalMapSize) {
                        retryMap = subMap(ordinalMap, successLength + 1, ordinalMapSize);
                    }
                }
            }
        }
        return retryMap;
    }

    /**
     * 处理mysql批量提交异常
     */
    private <T> Map<Integer, T> handleMysqlBatchUpdateException(BatchCommitConfig batchCommitConfig, Map<Integer, T> ordinalMap,
                                                                BatchUpdateException batchUpdateException, BatchResult<T> batchResult) throws BatchUpdateException {
        boolean isSingleCommit = checkBatchUpdateException(batchCommitConfig, batchUpdateException);
        int ordinalMapSize = ordinalMap.size();
        // 判断mysql是否使用了批量操作,mysql使用批量操作时不能准确判断成功数据因此出现错误时使用单条插入的方式
        Map<Integer, T> failMap = new TreeMap<>();
        Map<Integer, T> retryMap = null;
        int[] updateCounts = batchUpdateException.getUpdateCounts();
        for (int i = 0; i < updateCounts.length; i++) {
            Map<Integer, T> subMap = subMap(ordinalMap, i, null);
            if (updateCounts[i] == SqlExceptionErrorCodeConstant.SQL_EXECUTE_FAIL_STATUS) {
                if (!batchCommitConfig.useBatch) {
                    failMap.putAll(subMap);
                    batchResult.addFailDatas(subMap);
                } else if (!isSingleCommit || !singleCommit(batchCommitConfig, subMap.values().toArray()[0])) {
                    // 为了减少执行次数，当遇到执行失败数据时把剩余未执行的数据重新批量处理
                    batchResult.addFailDatas(subMap);
                    retryMap = subMap(ordinalMap, i + 1, ordinalMapSize);
                    break;
                } else {
                    batchResult.addSuccessDatas(subMap);
                }
            } else {
                batchResult.addSuccessDatas(subMap);
            }
        }
        if (!failMap.isEmpty() && logger.isErrorEnabled()) {
            logger.error("Batch processing data failed, datasource name: {}, error code: {}, sqlId: {}, fail data: {}", batchCommitConfig.datasourceName,
                    batchUpdateException.getErrorCode(), batchCommitConfig.sqlId, toJsonString(failMap), batchUpdateException);
        }
        return retryMap;
    }

    /**
     * 单条数据执行数据库操作
     */
    private <T> boolean singleCommit(BatchCommitConfig batchCommitConfig, T t) {
        try {
            BoundSql boundSql = batchCommitConfig.mappedStatement.getBoundSql(t);
            String sql = boundSql.getSql();
            try (PreparedStatement preparedStatement = batchCommitConfig.connection.prepareStatement(sql)) {
                DefaultParameterHandler defaultParameterHandler = new DefaultParameterHandler(batchCommitConfig.mappedStatement, t, boundSql);
                defaultParameterHandler.setParameters(preparedStatement);
                preparedStatement.executeUpdate();
            }
        } catch (Exception e) {
            logger.error("Single data execution failure after batch processing data fail, datasource name: {}, sqlId: {}, fail data: {}",
                    batchCommitConfig.datasourceName, batchCommitConfig.sqlId, toJsonString(t), e);
            return false;
        }
        return true;
    }

    private <T> Map<Integer, T> convertOrdinalMap(List<T> list) {
        int listSize;
        if (list != null && (listSize = list.size()) > 0) {
            Map<Integer, T> ordinalMap = new TreeMap<>();
            for (int i = 0; i < listSize; i++) {
                ordinalMap.put(i, list.get(i));
            }
            return ordinalMap;
        }
        return null;
    }

    private <T> Map<Integer, T> subMap(Map<Integer, T> ordinalMap, Integer start, Integer end) {
        Map<Integer, T> subOrdinalMap = new TreeMap<>();
        boolean isEmpty = start == null || (end != null && (end <= 0 || start.equals(end)));
        if (isEmpty) {
            return subOrdinalMap;
        }
        int count = 0;
        for (Map.Entry<Integer, T> entry : ordinalMap.entrySet()) {
            if (end != null) {
                if (count >= start && count < end) {
                    subOrdinalMap.put(entry.getKey(), entry.getValue());
                }
            } else if (count == start) {
                subOrdinalMap.put(entry.getKey(), entry.getValue());
                break;
            }
            count++;
        }
        return subOrdinalMap;
    }

    public BatchCommit(EasyMsMultiDataSource easyMsMultiDataSource, SqlSessionFactory sqlSessionFactory) {
        this.easyMsMultiDataSource = easyMsMultiDataSource;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    public SqlSessionFactory getSqlSessionFactory() {
        return this.sqlSessionFactory;
    }

    private final class BatchCommitConfig {

        private final String datasourceName;

        private final DatabaseType databaseType;

        private final boolean useBatch;

        private final EasyMsDataSource easyMsDataSource;

        private final DataSourceTransactionManager transactionManager;

        private final int perCommitSize;

        private final boolean asWhole;

        private final Configuration configuration;

        private String sqlId;

        private MappedStatement mappedStatement;

        private KeyGenerator keyGenerator;

        private Connection connection;

        private BatchCommitConfig(BatchCommitConfig batchCommitConfig, String sqlId) {
            this.datasourceName = batchCommitConfig.datasourceName;
            this.databaseType = batchCommitConfig.databaseType;
            this.useBatch = batchCommitConfig.useBatch;
            this.easyMsDataSource = batchCommitConfig.easyMsDataSource;
            this.perCommitSize = batchCommitConfig.perCommitSize;
            this.asWhole = batchCommitConfig.asWhole;
            this.configuration = batchCommitConfig.configuration;
            this.transactionManager = batchCommitConfig.transactionManager;
            this.sqlId = sqlId;
            this.mappedStatement = this.configuration.getMappedStatement(sqlId);
            this.keyGenerator = this.mappedStatement.getKeyGenerator();
            this.connection = getConnection();
        }

        private BatchCommitConfig(String datasourceName, String sqlId, int perCommitSize, boolean asWhole) {
            this(datasourceName, perCommitSize, asWhole);
            this.sqlId = sqlId;
            this.mappedStatement = this.configuration.getMappedStatement(sqlId);
            this.keyGenerator = this.mappedStatement.getKeyGenerator();
            this.connection = getConnection();
        }

        private BatchCommitConfig(String datasourceName, int perCommitSize, boolean asWhole) {
            this.datasourceName = datasourceName;
            EasyMsMasterSlaveDataSource easyMsMasterSlaveDataSource = easyMsMultiDataSource.getDataSource(datasourceName);
            this.databaseType = easyMsMasterSlaveDataSource.getDatabaseType();
            this.easyMsDataSource = easyMsMasterSlaveDataSource.getMasterDataSource();
            if (DatabaseType.ORACLE == this.databaseType) {
                this.useBatch = true;
            } else {
                String url = this.easyMsDataSource.getUrl();
                if (url.contains(MYSQL_BATCH_PARAM)) {
                    int batchParamIndex = url.lastIndexOf(MYSQL_BATCH_PARAM) + MYSQL_BATCH_PARAM.length() + 1;
                    int ampersandIndex = url.indexOf('&', batchParamIndex);
                    this.useBatch = ampersandIndex != -1 ?
                            Boolean.parseBoolean(url.substring(batchParamIndex, ampersandIndex).trim()) : Boolean.parseBoolean(url.substring(batchParamIndex).trim());
                } else {
                    this.useBatch = true;
                }
            }
            this.perCommitSize = perCommitSize;
            this.asWhole = asWhole;
            this.configuration = sqlSessionFactory.getConfiguration();
            this.transactionManager = new DataSourceTransactionManager(this.easyMsDataSource);
        }

        /**
         * 获取数据库连接
         */
        private Connection getConnection() {
            Connection newConnection;
            ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(easyMsDataSource);
            if (connectionHolder != null) {
                newConnection = connectionHolder.getConnection();
            } else {
                TRANSACTION_STATUS.set(transactionManager.getTransaction(new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW)));
                connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(easyMsDataSource);
                if (connectionHolder != null) {
                    newConnection = connectionHolder.getConnection();
                } else {
                    TRANSACTION_STATUS.remove();
                    throw new BatchCommitRuntimeException("Get datasource '{}' connection failure!", datasourceName);
                }
            }
            return mappedStatement.getStatementLog().isDebugEnabled() ?
                    ConnectionLogger.newInstance(newConnection, mappedStatement.getStatementLog(), 0) : newConnection;
        }
    }

    private final class PreparedStatementHolder<T> {

        private final PreparedStatement preparedStatement;

        private final Map<Integer, T> subOrdinalMap;

        private final AtomicLong count;

        private PreparedStatementHolder(PreparedStatement preparedStatement) {
            this.preparedStatement = preparedStatement;
            this.subOrdinalMap = new TreeMap<>();
            this.count = new AtomicLong(0);
        }
    }

    /**
     * 提交事务
     */
    private void commitTransaction(BatchCommitConfig batchCommitConfig) {
        final TransactionStatus currentTransactionStatus = TRANSACTION_STATUS.get();
        if (currentTransactionStatus != null && !currentTransactionStatus.isCompleted()) {
            if (currentTransactionStatus.isRollbackOnly()) {
                batchCommitConfig.transactionManager.rollback(currentTransactionStatus);
            } else {
                batchCommitConfig.transactionManager.commit(currentTransactionStatus);
            }
        }
    }

    /**
     * 回滚事务
     */
    private void rollbackTransaction(BatchCommitConfig batchCommitConfig) {
        final TransactionStatus currentTransactionStatus = TRANSACTION_STATUS.get();
        if (currentTransactionStatus != null) {
            if (!currentTransactionStatus.isCompleted()) {
                batchCommitConfig.transactionManager.rollback(currentTransactionStatus);
            }
        } else {
            ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(batchCommitConfig.easyMsDataSource);
            if (connectionHolder != null) {
                connectionHolder.setRollbackOnly();
            }
        }
    }

    private boolean checkBatchUpdateException(BatchCommitConfig batchCommitConfig, BatchUpdateException batchUpdateException) throws BatchUpdateException {
        if (batchCommitConfig.asWhole) {
            throw batchUpdateException;
        }
        // 当出现表不存在等整体无法处理的情况下，其他异常都使用单条提交
        if (DatabaseType.ORACLE == batchCommitConfig.databaseType) {
            if (SqlExceptionErrorCodeConstant.ORACLE_ERROR_CODE_THROW.contains(batchUpdateException.getErrorCode())) {
                throw batchUpdateException;
            }
            return SqlExceptionErrorCodeConstant.ORACLE_ERROR_CODE_SINGLE.contains(batchUpdateException.getErrorCode());
        } else if (DatabaseType.MYSQL_5 == batchCommitConfig.databaseType || DatabaseType.MYSQL_8 == batchCommitConfig.databaseType) {
            if (SqlExceptionErrorCodeConstant.MYSQL_ERROR_CODE_THROW.contains(batchUpdateException.getErrorCode())) {
                throw batchUpdateException;
            }
            // mysql默认开启失败后单条提交
            return true;
        } else {
            throw batchUpdateException;
        }
    }

    private <T> String toJsonString(Map<Integer, T> subMap) {
        List<String> subList = new ArrayList<>();
        if (subMap != null) {
            subMap.values().forEach(t -> {
                if (t instanceof String) {
                    subList.add((String) t);
                } else {
                    subList.add(JSON.toJSONString(t));
                }
            });
        }
        return subList.toString();
    }

    private <T> String toJsonString(T t) {
        if (t instanceof String) {
            return (String) t;
        }
        return JSON.toJSONString(t);
    }
}
